import * as fs from "fs/promises";
import * as path from "path";
import * as diff from "diff";
import { config } from "../config";
import { logger } from "../utils/logger";
import { WorkspaceService } from "./workspace-service";
import {
  FileReadResponse,
  FileWriteResponse,
  SearchReplaceResponse,
  FileDeleteResponse,
  FileStatsResponse,
  DirectoryListResponse,
  DirectoryEntry,
  RecursiveDirectoryListing,
  RecursiveDirectoryEntry,
  MAX_LINES_PER_READ,
} from "@repo/types";

export class FileService {
  constructor(private workspaceService: WorkspaceService) {}

  /**
   * Calculate accurate line changes using diff library
   */
  private calculateDiffStats(
    oldContent: string,
    newContent: string
  ): { linesAdded: number; linesRemoved: number } {
    const changes = diff.diffLines(oldContent, newContent);
    let linesAdded = 0;
    let linesRemoved = 0;

    for (const change of changes) {
      if (change.added) {
        linesAdded += change.count || 0;
      } else if (change.removed) {
        linesRemoved += change.count || 0;
      }
    }

    // Log the diff calculation with emoji
    logger.info(`📊 Diff calculated: +${linesAdded} -${linesRemoved} lines`);

    return { linesAdded, linesRemoved };
  }

  /**
   * Read file contents with optional line range
   */
  async readFile(
    relativePath: string,
    shouldReadEntireFile: boolean = true,
    startLine?: number,
    endLine?: number
  ): Promise<FileReadResponse> {
    try {
      const fullPath = this.workspaceService.resolvePath(relativePath);

      // Check file size before reading
      const stats = await fs.stat(fullPath);
      const maxSizeBytes = config.maxFileSizeMB * 1024 * 1024;

      if (stats.size > maxSizeBytes) {
        return {
          success: false,
          message: `File too large: ${stats.size} bytes (max: ${maxSizeBytes} bytes)`,
          error: "FILE_TOO_LARGE",
        };
      }

      const content = await fs.readFile(fullPath, "utf-8");
      const lines = content.split("\n");

      if (shouldReadEntireFile) {
        return {
          success: true,
          content,
          totalLines: lines.length,
          message: `Read entire file: ${relativePath} (${lines.length} lines)`,
        };
      }

      // Handle line range reading with clamping and pagination (max lines per page)
      const requestedStart = startLine ?? 1;
      const safeStart = Math.max(
        1,
        Math.min(requestedStart, Math.max(1, lines.length))
      );
      const requestedEnd = endLine ?? safeStart + MAX_LINES_PER_READ - 1;
      const safeEnd = Math.min(
        requestedEnd,
        safeStart + MAX_LINES_PER_READ - 1,
        lines.length
      );

      const startIdx = safeStart - 1;
      const endIdx = safeEnd; // slice end is exclusive

      const selectedLines = lines.slice(startIdx, endIdx);
      const selectedContent = selectedLines.join("\n");

      return {
        success: true,
        content: selectedContent,
        startLine: safeStart,
        endLine: safeEnd,
        totalLines: lines.length,
        message: `Read lines ${safeStart}-${safeEnd} of ${relativePath}`,
      };
    } catch (error) {
      logger.error("Failed to read file", { relativePath, error });

      if (error instanceof Error && error.message.includes("ENOENT")) {
        return {
          success: false,
          message: `File not found: ${relativePath}`,
          error: "FILE_NOT_FOUND",
        };
      }

      return {
        success: false,
        message: `Failed to read file: ${relativePath}`,
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }

  /**
   * Get file stats (size, modification time, type)
   */
  async getFileStats(relativePath: string): Promise<FileStatsResponse> {
    try {
      const fullPath = this.workspaceService.resolvePath(relativePath);
      const stats = await fs.stat(fullPath);

      return {
        success: true,
        stats: {
          size: stats.size,
          mtime: stats.mtime.toISOString(),
          isFile: stats.isFile(),
          isDirectory: stats.isDirectory(),
        },
        message: `Retrieved stats for: ${relativePath} (${stats.size} bytes)`,
      };
    } catch (error) {
      logger.error("Failed to get file stats", { relativePath, error });

      if (error instanceof Error && error.message.includes("ENOENT")) {
        return {
          success: false,
          message: `File not found: ${relativePath}`,
          error: "FILE_NOT_FOUND",
        };
      }

      return {
        success: false,
        message: `Failed to get file stats: ${relativePath}`,
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }

  /**
   * Write file contents
   */
  async writeFile(
    relativePath: string,
    content: string,
    instructions: string
  ): Promise<FileWriteResponse> {
    try {
      const fullPath = this.workspaceService.resolvePath(relativePath);

      // Check if this is a new file and get existing content
      let isNewFile = false;
      let existingContent = "";

      try {
        existingContent = await fs.readFile(fullPath, "utf-8");
      } catch {
        isNewFile = true;
      }

      // Ensure directory exists
      const dirPath = path.dirname(fullPath);
      await fs.mkdir(dirPath, { recursive: true });

      // Write the file
      await fs.writeFile(fullPath, content, "utf-8");

      // Calculate line changes
      let linesAdded: number;
      let linesRemoved: number;

      if (isNewFile) {
        linesAdded = content.split("\n").length;
        linesRemoved = 0;
      } else {
        const diffStats = this.calculateDiffStats(existingContent, content);
        linesAdded = diffStats.linesAdded;
        linesRemoved = diffStats.linesRemoved;
      }

      logger.info("File written", {
        relativePath,
        isNewFile,
        instructions,
        linesAdded,
        linesRemoved,
      });

      return {
        success: true,
        message: isNewFile
          ? `Created new file: ${relativePath}`
          : `Modified file: ${relativePath}`,
        isNewFile,
        linesAdded,
        linesRemoved,
      };
    } catch (error) {
      logger.error("Failed to write file", { relativePath, error });

      return {
        success: false,
        message: `Failed to write file: ${relativePath}`,
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }

  /**
   * Delete a file
   */
  async deleteFile(relativePath: string): Promise<FileDeleteResponse> {
    try {
      const fullPath = this.workspaceService.resolvePath(relativePath);

      try {
        await fs.unlink(fullPath);
        logger.info("File deleted", { relativePath });

        return {
          success: true,
          message: `Successfully deleted file: ${relativePath}`,
        };
      } catch (error) {
        if (error instanceof Error && error.message.includes("ENOENT")) {
          return {
            success: true,
            message: `File does not exist: ${relativePath}`,
            wasAlreadyDeleted: true,
          };
        }
        throw error;
      }
    } catch (error) {
      logger.error("Failed to delete file", { relativePath, error });

      return {
        success: false,
        message: `Failed to delete file: ${relativePath}`,
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }

  /**
   * Search and replace text in a file
   */
  async searchReplace(
    relativePath: string,
    oldString: string,
    newString: string
  ): Promise<SearchReplaceResponse> {
    try {
      // Input validation
      if (!oldString) {
        return {
          success: false,
          message: "Old string cannot be empty",
          error: "EMPTY_OLD_STRING",
          isNewFile: false,
          linesAdded: 0,
          linesRemoved: 0,
          occurrences: 0,
          oldLength: 0,
          newLength: 0,
        };
      }

      if (oldString === newString) {
        return {
          success: false,
          message: "Old string and new string are identical",
          error: "IDENTICAL_STRINGS",
          isNewFile: false,
          linesAdded: 0,
          linesRemoved: 0,
          occurrences: 0,
          oldLength: 0,
          newLength: 0,
        };
      }

      const fullPath = this.workspaceService.resolvePath(relativePath);

      // Read existing content
      let existingContent: string;
      try {
        existingContent = await fs.readFile(fullPath, "utf-8");
      } catch (error) {
        return {
          success: false,
          message: `File not found: ${relativePath}`,
          error: error instanceof Error ? error.message : "File read error",
          isNewFile: false,
          linesAdded: 0,
          linesRemoved: 0,
          occurrences: 0,
          oldLength: 0,
          newLength: 0,
        };
      }

      // Count occurrences
      const occurrences = existingContent.split(oldString).length - 1;

      if (occurrences === 0) {
        return {
          success: false,
          message: `Text not found in file: ${relativePath}`,
          error: "TEXT_NOT_FOUND",
          isNewFile: false,
          linesAdded: 0,
          linesRemoved: 0,
          occurrences: 0,
          oldLength: existingContent.length,
          newLength: existingContent.length,
        };
      }

      if (occurrences > 1) {
        return {
          success: false,
          message: `Multiple occurrences found (${occurrences}). The old_string must be unique.`,
          error: "TEXT_NOT_UNIQUE",
          isNewFile: false,
          linesAdded: 0,
          linesRemoved: 0,
          occurrences,
          oldLength: existingContent.length,
          newLength: existingContent.length,
        };
      }

      // Perform replacement and calculate metrics
      const newContent = existingContent.replace(oldString, newString);

      // Calculate accurate line changes using diff
      const { linesAdded, linesRemoved } = this.calculateDiffStats(
        existingContent,
        newContent
      );

      // Write the new content
      await fs.writeFile(fullPath, newContent);

      logger.info("Search and replace completed", {
        relativePath,
        occurrences,
        linesAdded,
        linesRemoved,
        oldLength: existingContent.length,
        newLength: newContent.length,
      });

      return {
        success: true,
        message: `Successfully replaced text in ${relativePath}: ${occurrences} occurrence(s), ${linesAdded} lines added, ${linesRemoved} lines removed`,
        isNewFile: false,
        linesAdded,
        linesRemoved,
        occurrences,
        oldLength: existingContent.length,
        newLength: newContent.length,
      };
    } catch (error) {
      logger.error("Failed to search and replace", { relativePath, error });

      return {
        success: false,
        message: `Failed to search and replace in file: ${relativePath}`,
        error: error instanceof Error ? error.message : "Unknown error",
        isNewFile: false,
        linesAdded: 0,
        linesRemoved: 0,
        occurrences: 0,
        oldLength: 0,
        newLength: 0,
      };
    }
  }

  /**
   * List directory contents
   */
  async listDirectory(relativePath: string): Promise<DirectoryListResponse> {
    try {
      const fullPath = this.workspaceService.resolvePath(relativePath);
      const entries = await fs.readdir(fullPath, { withFileTypes: true });

      const contents: DirectoryEntry[] = entries.map((entry) => ({
        name: entry.name,
        type: entry.isDirectory() ? "directory" : "file",
        isDirectory: entry.isDirectory(),
      }));

      // Sort: directories first, then files, alphabetically
      contents.sort((a, b) => {
        if (a.isDirectory && !b.isDirectory) return -1;
        if (!a.isDirectory && b.isDirectory) return 1;
        return a.name.localeCompare(b.name);
      });

      return {
        success: true,
        path: relativePath,
        contents,
        message: `Listed ${contents.length} items in ${relativePath}`,
      };
    } catch (error) {
      logger.error("Failed to list directory", { relativePath, error });

      if (error instanceof Error && error.message.includes("ENOENT")) {
        return {
          success: false,
          path: relativePath,
          message: `Directory not found: ${relativePath}`,
          error: "DIRECTORY_NOT_FOUND",
        };
      }

      return {
        success: false,
        path: relativePath,
        message: `Failed to list directory: ${relativePath}`,
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }

  /**
   * Recursively list all files and directories in a tree
   */
  async listDirectoryRecursive(
    relativePath: string = "."
  ): Promise<RecursiveDirectoryListing> {
    // Folders to ignore while walking the repository
    const IGNORE_DIRS = [
      "node_modules",
      ".git",
      ".next",
      ".turbo",
      "dist",
      "build",
    ];

    const entries: RecursiveDirectoryEntry[] = [];

    const walkDirectory = async (currentPath: string): Promise<void> => {
      try {
        const fullPath = this.workspaceService.resolvePath(currentPath);
        const dirEntries = await fs.readdir(fullPath, { withFileTypes: true });

        for (const entry of dirEntries) {
          // Skip ignored directories
          if (IGNORE_DIRS.includes(entry.name)) continue;

          const entryPath =
            currentPath === "." ? entry.name : `${currentPath}/${entry.name}`;

          entries.push({
            name: entry.name,
            type: entry.isDirectory() ? "directory" : "file",
            relativePath: entryPath,
            isDirectory: entry.isDirectory(),
          });

          // Recursively walk subdirectories
          if (entry.isDirectory()) {
            await walkDirectory(entryPath);
          }
        }
      } catch (error) {
        logger.error("Failed to walk directory", { currentPath, error });
        // Continue walking other directories even if one fails
      }
    };

    try {
      await walkDirectory(relativePath);

      // Sort entries: directories first, then files, both alphabetically by relative path
      entries.sort((a, b) => {
        if (a.isDirectory !== b.isDirectory) {
          return a.isDirectory ? -1 : 1;
        }
        return a.relativePath.localeCompare(b.relativePath);
      });

      logger.info("Recursive directory listing completed", {
        basePath: relativePath,
        totalCount: entries.length,
      });

      return {
        success: true,
        entries,
        basePath: relativePath,
        totalCount: entries.length,
        message: `Recursively listed ${entries.length} items starting from ${relativePath}`,
      };
    } catch (error) {
      logger.error("Failed to list directory recursively", {
        relativePath,
        error,
      });

      if (error instanceof Error && error.message.includes("ENOENT")) {
        return {
          success: false,
          entries: [],
          basePath: relativePath,
          totalCount: 0,
          message: `Directory not found: ${relativePath}`,
          error: "DIRECTORY_NOT_FOUND",
        };
      }

      return {
        success: false,
        entries: [],
        basePath: relativePath,
        totalCount: 0,
        message: `Failed to list directory recursively: ${relativePath}`,
        error: error instanceof Error ? error.message : "Unknown error",
      };
    }
  }
}

export default FileService;
